package com.smilingfrog.sagetv.scrobbler;

import gkusnick.sagetv.api.API;
import gkusnick.sagetv.api.MediaFileAPI;
import net.roarsoftware.lastfm.Caller;
import net.roarsoftware.lastfm.scrobble.ResponseStatus;
import net.roarsoftware.lastfm.scrobble.Scrobbler;
import net.roarsoftware.lastfm.scrobble.Source;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import sage.SageTVPlugin;
import sage.SageTVPluginRegistry;

import java.io.IOException;
import java.net.*;
import java.util.List;
import java.util.Map;

public class Plugin implements SageTVPlugin
{
  private static Logger LOG = null;

  private static final String CLIENT_ID = "stv";
  private static final String VERSION_NUMBER = "0.2";
  private static final String USER_AGENT = CLIENT_ID + " " + VERSION_NUMBER;
  private static final String SCROBBLE = "scrobbler/scrobble";
  private static final String USERNAME = "scrobbler/username";
  private static final String PASSWORD = "scrobbler/password";
  private static final String DEFAULT_SCROBBLE = "false";
  private static final String DEFAULT_USERNAME = "";
  private static final String DEFAULT_PASSWORD = "";
  private static final String PLAYBACK_STARTED = "PlaybackStarted";
  private static final String PLAYBACK_FINISHED = "PlaybackFinished";
  private static final String TRACK = "Track";
  private static final String TRUE = "true";
  private static final String FALSE = "false";

  private SageTVPluginRegistry registry;
  private Scrobbler scrobbler;
  private boolean creationFailed;

  public Plugin(SageTVPluginRegistry registry)
  {
    this.registry = registry;
    if (null == LOG)
    {
      PropertyConfigurator.configure("plugins/scrobbler/scrobbler.log4j.properties");
      LOG = Logger.getLogger(Plugin.class);
    }

    if (LOG.isDebugEnabled())
    {
      getCaller().setDebugMode(true);
    }

    getCaller().setUserAgent(USER_AGENT);

    try
    {
      System.setProperty("java.net.useSystemProxies", "true");
      List<Proxy> proxies = ProxySelector.getDefault().select(new URI("http://www.sagetv.com/"));
      Proxy proxy = proxies.isEmpty() ? null : proxies.get(0);
      getCaller().setProxy(proxy);
    }
    catch (Exception e)
    {
      creationFailed = true;
      LOG.error("Could not establish proxy settings", e);
    }

    scrobbler = Scrobbler.newScrobbler(CLIENT_ID, VERSION_NUMBER, getConfigValue(USERNAME));
    try
    {
      ResponseStatus status = scrobbler.handshake(getConfigValue(PASSWORD));
      if (null == status || !status.ok())
      {
        creationFailed = true;
        if (null != status)
        {
          LOG.error("Could not authenticate with last.fm: " + status.getMessage());
        }
        else
        {
          LOG.error("Could not authenticate with last.fm");
        }
      }
    }
    catch (Exception e)
    {
      creationFailed = true;
      LOG.error("Could not authenticate with last.fm", e);
    }

    if (!creationFailed)
    {
      LOG.info("Plugin created successfully");
    }
    else
    {
      LOG.error("Plugin created with errors");
    }
  }

  public Plugin(SageTVPluginRegistry registry, boolean reset)
  {
    this(registry);
    if (reset)
    {
      resetConfig();
    }
  }

  private Caller getCaller()
  {
    return Caller.getInstance();
  }

  public void start()
  {
    LOG.debug("Starting Plugin");
    registry.eventSubscribe(this, PLAYBACK_STARTED);
    registry.eventSubscribe(this, PLAYBACK_FINISHED);
    LOG.debug("Started Plugin");
  }

  public void stop()
  {
    LOG.debug("Stopping Plugin");
    registry.eventUnsubscribe(this, PLAYBACK_STARTED);
    registry.eventUnsubscribe(this, PLAYBACK_FINISHED);
    LOG.debug("Stopped Plugin");
  }

  public void destroy()
  {
    LOG.debug("Destroying Plugin");
    LOG.debug("Destroyed Plugin");
  }

  public String[] getConfigSettings()
  {
    LOG.debug("Getting configuration settings");
    return (new String[] {
        SCROBBLE,
        USERNAME,
        PASSWORD
    });
  }

  public String getConfigValue(String setting)
  {
    LOG.debug("Getting value for setting \"" + setting + "\"");
    String defaultValue = null;
    if (SCROBBLE.equals(setting))
    {
      defaultValue = DEFAULT_SCROBBLE;
    }
    else if (USERNAME.equals(setting))
    {
      defaultValue = DEFAULT_USERNAME;
    }
    else if (PASSWORD.equals(setting))
    {
      defaultValue = DEFAULT_PASSWORD;
    }
    String value = API.apiNullUI.configuration.GetProperty(setting, defaultValue);
    String valueToLog = PASSWORD.equals(setting) ? "secret" : value;
    LOG.debug("Returning value of \"" + valueToLog + "\" for setting \"" + setting + "\"");
    return value;
  }

  public String[] getConfigValues(String setting)
  {
    LOG.debug("Getting values for setting \"" + setting + "\"");
    return null;
  }

  public int getConfigType(String setting)
  {
    LOG.debug("Getting type for setting \"" + setting + "\"");
    if (SCROBBLE.equals(setting))
    {
      return SageTVPlugin.CONFIG_BOOL;
    }
    else if (USERNAME.equals(setting))
    {
      return SageTVPlugin.CONFIG_TEXT;
    }
    else if (PASSWORD.equals(setting))
    {
      return SageTVPlugin.CONFIG_PASSWORD;
    }
    else
    {
      throw new IllegalArgumentException("Unknown setting: " + setting);
    }
  }

  public void setConfigValue(String setting, String value)
  {
    String valueToLog = PASSWORD.equals(setting) ? "secret" : value;
    LOG.debug("Setting value of \"" + valueToLog + "\" for setting \"" + setting + "\"");
    API.apiNullUI.configuration.SetProperty(setting, value);
  }

  public void setConfigValues(String setting, String[] values)
  {
    LOG.debug("Setting values of \"" + values + "\" for setting \"" + setting + "\"");
  }

  public String[] getConfigOptions(String setting)
  {
    LOG.debug("Getting options for setting \"" + setting + "\"");
    return null;
  }

  public String getConfigHelpText(String setting)
  {
    LOG.debug("Getting help text for setting \"" + setting + "\"");
    if (SCROBBLE.equals(setting))
    {
      return "Allow the plugin to send data to last.fm.";
    }
    else if (USERNAME.equals(setting))
    {
      return "Your last.fm user name.";
    }
    else if (PASSWORD.equals(setting))
    {
      return "Your last.fm password.";
    }
    else
    {
      return "No help available.";
    }
  }

  public String getConfigLabel(String setting)
  {
    LOG.debug("Getting label for setting \"" + setting + "\"");
    if (SCROBBLE.equals(setting))
    {
      return "Scrobble";
    }
    else if (USERNAME.equals(setting))
    {
      return "Username";
    }
    else if (PASSWORD.equals(setting))
    {
      return "Password";
    }
    else
    {
      return "No Label Available";
    }
  }

  public void resetConfig()
  {
    LOG.debug("Resetting configuration");
    setConfigValue(SCROBBLE, DEFAULT_SCROBBLE);
    setConfigValue(USERNAME, DEFAULT_USERNAME);
    setConfigValue(PASSWORD, DEFAULT_PASSWORD);
    LOG.info("Reset configuration successfully");
  }

  /**
   * PlaybackStarted - vars: MediaFile, UIContext[String], Duration[Long], MediaTime[Long], ChapterNum[Integer], TitleNum[Integer]
   * PlaybackFinished (called at the EOF) - vars: MediaFile, UIContext[String], Duration[Long], MediaTime[Long], ChapterNum[Integer], TitleNum[Integer]
   *
   * @param eventName The name of the event.
   * @param eventVars The additional variables associated with the event.
   */
  public void sageEvent(String eventName, Map eventVars)
  {
    LOG.debug("Scrobble: " + getConfigValue(SCROBBLE) + ", Event: " + eventName);
    if (TRUE.equals(getConfigValue(SCROBBLE)) && !creationFailed)
    {
      LOG.debug("Handling event: " + eventName + ": " + eventVars);
      MediaFileAPI.MediaFile mediaFile = API.apiNullUI.mediaFileAPI.Wrap(eventVars.get("MediaFile"));
      if (mediaFile.IsMusicFile())
      {
        StringBuilder trackInfo = new StringBuilder();
        if (PLAYBACK_STARTED.equals(eventName))
        {
          LOG.debug("Playback Started");
          try
          {
            String artist = mediaFile.GetAlbumForFile().GetAlbumArtist();
            String track = mediaFile.GetMediaTitle();
            String album = mediaFile.GetAlbumForFile().GetAlbumName();
            int length = (int) (mediaFile.GetFileDuration() / 1000);
            int trackNumber = Integer.parseInt(mediaFile.GetMediaFileMetadata(TRACK));
            trackInfo.append("Artist=").append(artist).append(", ");
            trackInfo.append("Track=").append(track).append(", ");
            trackInfo.append("Album=").append(album).append(", ");
            trackInfo.append("Length=").append(length).append(", ");
            trackInfo.append("TrackNumber=").append(trackNumber);

            ResponseStatus status = scrobbler.nowPlaying(artist, track, album, length, trackNumber);

            if (status.ok())
            {
              LOG.info("Sent now playing information to last.fm: " + trackInfo.toString());
            }
            else
            {
              LOG.error("Failed to send now playing information to last.fm: " + trackInfo.toString() + ": " + status.getMessage());
            }
          }
          catch (IOException e)
          {
            LOG.error("Failed to send now playing information to last.fm: " + trackInfo.toString(), e);
          }
        }
        else if (PLAYBACK_FINISHED.equals(eventName))
        {
          LOG.debug("Playback Finished");
          try
          {
            String artist = mediaFile.GetAlbumForFile().GetAlbumArtist();
            String track = mediaFile.GetMediaTitle();
            String album = mediaFile.GetAlbumForFile().GetAlbumName();
            int length = (int) (mediaFile.GetFileDuration() / 1000);
            int trackNumber = Integer.parseInt(mediaFile.GetMediaFileMetadata(TRACK));
            long startTime = (System.currentTimeMillis() / 1000) - (length / 2);
            trackInfo.append("Artist=").append(artist).append(", ");
            trackInfo.append("Track=").append(track).append(", ");
            trackInfo.append("Album=").append(album).append(", ");
            trackInfo.append("Length=").append(length).append(", ");
            trackInfo.append("TrackNumber=").append(trackNumber).append(", ");
            trackInfo.append("StartTime=").append(startTime);

            ResponseStatus status = scrobbler.submit(artist, track, album, length, trackNumber, Source.USER, startTime);

            if (status.ok())
            {
              LOG.info("Sent submission information to last.fm: " + trackInfo.toString());
            }
            else
            {
              LOG.error("Failed to send submission information to last.fm: " + trackInfo.toString() + ": " + status.getMessage());
            }
          }
          catch (IOException e)
          {
            LOG.error("Failed to send submission information to last.fm: " + trackInfo.toString(), e);
          }
        }
      }
      else
      {
        LOG.debug("The media file was not considered a music file. No last.fm scrobbling performed.");
      }
      LOG.debug("Event handled: " + eventName);
    }
    else
    {
      LOG.debug("Plugin is disabled. Not handling event: " + eventName + ": " + eventVars);
    }
  }
}
